package dbb

import (
	"fmt"
	"reflect"
	"strconv"
	"strings"

	"github.com/gogf/gf/v2/util/gconv"
	"github.com/goki/ki/kit"
)

// StructFieldIsDef processses "def" tag for default value(s) of field
// defs = default values as strings as either comma-separated list of valid values
// or low:high value range (only for int or float numeric types)
// valPtr = pointer to value
// returns true if value is default, and string to add to tooltip for default values
func StructFieldIsDef(defs string, valPtr interface{}) (bool, string) {
	defStr := "[Def: " + defs + "] "
	def := false
	if strings.Contains(defs, ":") {
		dtags := strings.Split(defs, ":")
		lo, _ := strconv.ParseFloat(dtags[0], 64)
		hi, _ := strconv.ParseFloat(dtags[1], 64)
		switch fv := valPtr.(type) {
		case *float32:
			if lo <= float64(*fv) && float64(*fv) <= hi {
				def = true
			}
		case *float64:
			if lo <= *fv && *fv <= hi {
				def = true
			}
		case *int32:
			if lo <= float64(*fv) && float64(*fv) <= hi {
				def = true
			}
		case *int64:
			if lo <= float64(*fv) && float64(*fv) <= hi {
				def = true
			}
		case *int:
			if lo <= float64(*fv) && float64(*fv) <= hi {
				def = true
			}
		}
	} else {
		val := kit.ToStringPrec(valPtr, 6)
		dtags := strings.Split(defs, ",")
		for _, dv := range dtags {
			if dv == strings.TrimSpace(val) {
				def = true
				break
			}
		}
	}
	return def, defStr
}

// StructFieldVals represents field values in a struct, at multiple
// levels of depth potentially (represented by the Path field)
// used for StructNonDefFields for example.
type StructFieldVals struct {
	Path  string              `desc:"path of field.field parent fields to this field"`
	Field reflect.StructField `desc:"type information for field"`
	Val   reflect.Value       `desc:"value of field (as a pointer)"`
	Defs  string              `desc:"def tag information for default values"`
}
type MyStruct struct {
	Name string `def:"yh,cyy"`
	Age  int    `def:"7:20"`
	Addr int    `def:"30" min:"5" step:"1"`
}

func aa() {
	b := StructNonDefFields(&MyStruct{"yh1", 8, 35}, "")
	fmt.Printf("gconv.Strings(b): %v\n", gconv.Strings(b))
}

// StructNonDefFields processses "def" tag for default value(s)
// of fields in given struct and starting path, and returns all
// fields not at their default values.
// See also StructNoDefFieldsStr for a string representation of this information.
// Uses kit.FlatFieldsValueFunc to get all embedded fields.
// Uses a recursive strategy -- any fields that are themselves structs are
// expanded, and the field name represented by dots path separators.
func StructNonDefFields(structPtr interface{}, path string) []StructFieldVals {
	var flds []StructFieldVals
	kit.FlatFieldsValueFunc(structPtr, func(fval interface{}, typ reflect.Type, field reflect.StructField, fieldVal reflect.Value) bool {
		vvp := fieldVal.Addr()
		if field.Type.Kind() == reflect.Struct {
			spath := path
			if path != "" {
				spath += "."
			}
			spath += field.Name
			subs := StructNonDefFields(vvp.Interface(), spath)
			if len(subs) > 0 {
				flds = append(flds, subs...)
			}
			return true
		}
		dtag, got := field.Tag.Lookup("def")
		if !got {
			return true
		}
		def, defStr := StructFieldIsDef(dtag, vvp.Interface())
		if def {
			return true
		}
		flds = append(flds, StructFieldVals{Path: path, Field: field, Val: vvp, Defs: defStr})
		return true
	})
	return flds
}

// StructNonDefFieldsStr processses "def" tag for default value(s) of fields in
// given struct, and returns a string of all those not at their default values,
// in format: Path.Field: val // default value(s)
// Uses a recursive strategy -- any fields that are themselves structs are
// expanded, and the field name represented by dots path separators.
func StructNonDefFieldsStr(structPtr interface{}, path string) string {
	flds := StructNonDefFields(structPtr, path)
	if len(flds) == 0 {
		return ""
	}
	str := ""
	for _, fld := range flds {
		pth := fld.Path
		fnm := fld.Field.Name
		val := kit.ToStringPrec(fld.Val.Interface(), 6)
		dfs := fld.Defs
		if len(pth) > 0 {
			fnm = pth + "." + fnm
		}
		str += fmt.Sprintf("%s: %s // %s<br>\n", fnm, val, dfs)
	}
	return str
}
